package com.lizard.schedule.net.http.download;

import com.lizard.schedule.log.ScheduleLog;
import com.lizard.schedule.persistence.db.bean.DownloadFileInfo;
import com.lizard.schedule.persistence.db.bean.DownloadThreadInfo;
import com.lizard.schedule.persistence.db.table.DownloadFileInfoTbl;
import com.lizard.schedule.persistence.db.table.DownloadThreadInfoTbl;

import org.apache.http.HttpStatus;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 断点下载管理类
 *
 * 注：若检测到目标地址不支持断点下载则字段切换成单线程的非断点下载方式，非断点下载不支持暂停，即暂停等于取消下载
 *
 * 待完善：
 * 用ETag判断下载前后的文件完整性
 */
public class DownloadManager {

    private final String TAG = DownloadManager.class.getSimpleName();

    private static DownloadManager instance;

    /**
     * 下载的临时文件后缀
     */
    private final String TMP_SUFFIX = ".tmp";

    private ThreadPoolExecutor mExecutor;

    private int corePoolSize = 4;

    private int maximumPoolSize = 4;

    private long keepAliveTime = 0;

    private TimeUnit unit = TimeUnit.SECONDS;

    /**
     * 下载使用的buffer大小
     */
    private final int BUFFER_SIZE = 4096;

    private BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();

    /**
     * 下载任务Map<下载文件信息id, 该文件下载的所有线程任务>
     */
    private Map<Integer, List<DownloadTask>> mDownloadTasksMap = new HashMap<Integer, List<DownloadTask>>();

    private DownloadManager() {
        mExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public static DownloadManager getInstance() {
        if (instance == null) {
            synchronized (DownloadManager.class) {
                if (instance == null) {
                    instance = new DownloadManager();
                }
            }
        }
        return instance;
    }

    /**
     * 暂停下载
     *
     * @param url
     * @param saveDirPath
     * @param saveName
     */
    public void pause(String url, String saveDirPath, String saveName) {
        DownloadFileInfo downloadFileInfo = DownloadFileInfoTbl.getInstance()
                .select(url, new File(saveDirPath, saveName).getAbsolutePath());
        pause(downloadFileInfo);
    }

    /**
     * 暂停下载
     *
     * @param downloadFileInfo
     */
    private void pause(DownloadFileInfo downloadFileInfo) {
        if (downloadFileInfo != null) {
            List<DownloadTask> downloadTasks = mDownloadTasksMap.get(downloadFileInfo.get_id());
            if (downloadTasks != null) {
                for (DownloadTask downloadTask : downloadTasks) {
                    downloadTask.pause();
                }
            }
        }
    }

    /**
     * 结束下载(取消下载)
     * 注：已暂停的状态下将无法通知stop回调
     *
     * @param url
     * @param saveDirPath
     * @param saveName
     */
    public void stop(String url, String saveDirPath, String saveName) {
        DownloadFileInfo downloadFileInfo = DownloadFileInfoTbl.getInstance()
                .select(url, new File(saveDirPath, saveName).getAbsolutePath());

        if (downloadFileInfo != null) {
            boolean paused = true;
            List<DownloadTask> downloadTasks = mDownloadTasksMap.get(downloadFileInfo.get_id());
            if (downloadTasks != null) {
                for (DownloadTask downloadTask : downloadTasks) {
                    if (!downloadTask.isPaused()) {
                        paused = false;
                        break;
                    }
                }
            }
            if (paused) {   // 已暂停的直接删除记录即可
                deleteDownloadRecord(downloadFileInfo);
            } else {
                if (downloadTasks != null) {
                    for (DownloadTask downloadTask : downloadTasks) {
                        downloadTask.stop();
                    }
                }
            }
        }
    }

    /**
     * 判断是否正在下载
     *
     * @param url
     * @param saveDirPath
     * @param saveName
     * @return
     */
    public boolean isDownloading(String url, String saveDirPath, String saveName) {
        File saveFile = new File(saveDirPath, saveName);
        DownloadFileInfo downloadFileInfo = DownloadFileInfoTbl.getInstance().select(url, saveFile.getAbsolutePath());
        return isDownloading(downloadFileInfo);
    }

    /**
     * 判断是否正在下载
     *
     * @param downloadFileInfo
     * @return
     */
    public boolean isDownloading(DownloadFileInfo downloadFileInfo) {
        boolean isDownloading = false;
        if (downloadFileInfo != null && mDownloadTasksMap.containsKey(downloadFileInfo.get_id())) {
            isDownloading = true;
        }
        return isDownloading;
    }

    /**
     * 删除下载记录（内存、数据库）
     *
     * @param downloadFileInfo
     */
    private void deleteDownloadRecord(DownloadFileInfo downloadFileInfo) {
        if (downloadFileInfo != null) {
            // 内存记录删除
            mDownloadTasksMap.remove(downloadFileInfo.get_id());

            // DB记录删除
            DownloadThreadInfoTbl.getInstance().delete(downloadFileInfo.get_id());
            DownloadFileInfoTbl.getInstance().delete(downloadFileInfo.getUrl(), downloadFileInfo.getSavePath());

            // 临时文件删除
            File tmpFile = new File(downloadFileInfo.getSavePath() + TMP_SUFFIX);
            if (tmpFile.exists()) {
                tmpFile.delete();
            }
        }
    }

    /**
     * 计算下载进度百分比
     *
     * @param downloadFileInfo
     * @param downloadTasks
     * @return
     */
    private double getDownloadPercent(DownloadFileInfo downloadFileInfo, List<DownloadTask> downloadTasks) {
        int totalLength = 0;    // 该文件下载的所有线程任务下载的总长度
        for (DownloadTask downloadTask : downloadTasks) {
            totalLength += downloadTask.getDownloadThreadInfo().getLength();
        }
        double percent = totalLength * 1.0 / downloadFileInfo.getFileLength();
        percent = (int) (percent * 100) / 100.0;
        percent = percent > 100 ? 100 : percent;
        return percent;
    }

    /**
     * 下载
     * 注意：外部调用前先去检查SD卡是否存在(如果下载保存在SD卡)，本方法不做检查
     *
     * @param url              下载的网络地址
     * @param threadCount      下载该文件需要启动几个线程一起下载(最终同时下载的线程数由线程池决定)
     * @param saveDirPath      下载保存的文件夹路径
     * @param saveName         下载的保存的文件名
     * @param downloadListener 下载监听器
     */
    public void download(final String url,
                         final int threadCount,
                         final String saveDirPath,
                         final String saveName,
                         final DownloadListener downloadListener) {

        if (isDownloading(url, saveDirPath, saveName)) {
            //通知回调：已经在下载队列 不能重复下载
            ScheduleLog.e(TAG, "通知回调：已经在下载队列 不能重复下载");
            if (downloadListener != null) {
                downloadListener.exist();
            }
            return;
        }

        new Thread() {
            public void run() {
                DownloadFileInfo downloadFileInfo = null;
                HttpURLConnection conn = null;
                try {

                    // 最终采用的线程数数
                    int realThreadCount = threadCount;

                    //通知回调：等待下载
                    ScheduleLog.v(TAG, "通知回调：等待下载");
                    if (downloadListener != null) {
                        downloadListener.waiting();
                    }

                    File saveFile = new File(saveDirPath, saveName);
                    File tmpFile = new File(saveDirPath, saveName + TMP_SUFFIX);

                    downloadFileInfo = DownloadFileInfoTbl.getInstance().select(url, saveFile.getAbsolutePath());
                    List<DownloadThreadInfo> downloadThreadInfos;

                    if (downloadFileInfo == null) {   //  无下载目标文件的信息记录
                        conn = (HttpURLConnection) new URL(url).openConnection();
                        conn.setConnectTimeout(3000);
                        conn.setReadTimeout(3000);
                        conn.setRequestProperty("Range", "bytes=" + 0 + "-");

                        ScheduleLog.v(TAG, "Accept-Ranges:" + conn.getHeaderField("Accept-Ranges"));
                        ScheduleLog.v(TAG, "content-range:" + conn.getHeaderField("content-range"));
                        ScheduleLog.v(TAG, "ETag:" + conn.getHeaderField("ETag"));
                        ScheduleLog.v(TAG, "respCode:" + conn.getResponseCode());

                        int responseCode = conn.getResponseCode();

                        // 用于标记是否支持断线下载
                        boolean supportRanges = true;

                        // 判断是否支持断点下载
                        if(responseCode != HttpStatus.SC_PARTIAL_CONTENT){
                            // 不支持断点续传
                            supportRanges = false;
                        }

                        if(!supportRanges){ // 不支持断点下载
                            realThreadCount = 1;    // 非断点续传的只能一个线程

                            // 重新openConnection 不设置Range 才能获取正常的文件长度
                            conn = (HttpURLConnection) new URL(url).openConnection();
                            conn.setConnectTimeout(3000);
                            conn.setReadTimeout(3000);
                        }

                        downloadFileInfo = new DownloadFileInfo();
                        downloadFileInfo.setSavePath(saveFile.getAbsolutePath());
                        downloadFileInfo.setThreadCount(realThreadCount);
                        downloadFileInfo.setUrl(url);
                        downloadFileInfo.setSupportRanges(supportRanges);
                        downloadFileInfo.setFileLength(conn.getContentLength());
                        DownloadFileInfoTbl.getInstance().insert(downloadFileInfo);

                        // 重新从数据库获取 拿到id
                        downloadFileInfo = DownloadFileInfoTbl.getInstance()
                                .select(downloadFileInfo.getUrl(), downloadFileInfo.getSavePath());

                        // 清理相关线程信息 防止脏数据
                        DownloadThreadInfoTbl.getInstance().delete(downloadFileInfo.get_id());

                        // 若存在旧文件 则删除重建 防止脏文件
                        if (saveFile.exists()) {
                            saveFile.delete();
                        }
                        if (tmpFile.exists()) {
                            tmpFile.delete();
                        }
                    }

                    if (downloadFileInfo.getFileLength() <= 0) {
                        throw new Exception("file length = " + downloadFileInfo.getFileLength());
                    }

                    // 临时下载文件的创建
                    if (!tmpFile.exists()) {
                        if (!tmpFile.getParentFile().exists()) {
                            tmpFile.getParentFile().mkdirs();
                        }
                        tmpFile.createNewFile();
                    }

                    downloadThreadInfos = DownloadThreadInfoTbl.getInstance().select(downloadFileInfo.get_id());

                    if (downloadThreadInfos == null || downloadThreadInfos.size() <= 0) {
                        // 生成每一个线程信息
                        int lengthPerThread = downloadFileInfo.getFileLength() / downloadFileInfo.getThreadCount();
                        for (int i = 0; i < downloadFileInfo.getThreadCount(); i++) {
                            DownloadThreadInfo downloadThreadInfo = new DownloadThreadInfo();
                            downloadThreadInfo.setDownloadFileInfoId(downloadFileInfo.get_id());
                            downloadThreadInfo.setLength(0);
                            downloadThreadInfo.setStart(i * lengthPerThread);
                            if (i == downloadFileInfo.getThreadCount() - 1) { // 最后一个线程
                                downloadThreadInfo.setEnd(downloadFileInfo.getFileLength());
                            } else {
                                downloadThreadInfo.setEnd((i + 1) * lengthPerThread - 1);
                            }
                            DownloadThreadInfoTbl.getInstance().insert(downloadThreadInfo);

                        }

                        downloadThreadInfos = DownloadThreadInfoTbl.getInstance().select(downloadFileInfo.get_id());
                    }

                    // 开始下载/继续下载
                    List<DownloadTask> downloadTasks = new ArrayList<DownloadTask>();
                    for (DownloadThreadInfo downloadThreadInfo : downloadThreadInfos) {

                        DownloadTask downloadTask = new DownloadTask(downloadFileInfo,
                                downloadThreadInfo, tmpFile, downloadListener);
                        downloadTasks.add(downloadTask);
                    }
                    mDownloadTasksMap.put(downloadFileInfo.get_id(), downloadTasks);
                    // 通知回调：开始下载
                    ScheduleLog.v(TAG, "通知回调：开始下载 文件长度：" + downloadFileInfo.getFileLength());
                    if (downloadListener != null) {
                        double percent = getDownloadPercent(downloadFileInfo, downloadTasks);
                        downloadListener.begin(percent, downloadFileInfo.isSupportRanges());
                    }

                    // 开启线程下载
                    for (DownloadTask downloadTask : downloadTasks) {
                        mExecutor.execute(downloadTask);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    deleteDownloadRecord(downloadFileInfo);
                    // 通知回调：下载异常
                    ScheduleLog.v(TAG, "通知回调：下载异常");
                    if (downloadListener != null) {
                        downloadListener.error();
                    }
                } finally {
                    if (conn != null) {
                        conn.disconnect();
                    }
                }
            }
        }.start();
    }

    /**
     * 下载线程任务类
     */
    class DownloadTask implements Runnable {

        private final String TAG = DownloadTask.class.getSimpleName();

        /**
         * 下载的文件信息
         */
        private DownloadFileInfo mDownloadFileInfo;

        /**
         * 下载线程信息
         */
        private DownloadThreadInfo mDownloadThreadInfo;

        /**
         * 下载临时文件
         */
        private File mTmpFile;

        /**
         * 暂停标记 用来通知线程暂停
         */
        private boolean mPause = false;

        /**
         * 是否完成暂停动作
         */
        private boolean mPaused = false;

        /**
         * 停止下载标记 用来通知下载
         */
        private boolean mStop = false;

        /**
         * 是否完成了停止动作
         */
        private boolean mStoped = false;

        private DownloadListener mDownloadListener;

        public DownloadTask(DownloadFileInfo downloadFileInfo,
                            DownloadThreadInfo downloadThreadInfo,
                            final File tmpFile,
                            DownloadListener downloadListener) {
            mDownloadFileInfo = downloadFileInfo;
            mDownloadThreadInfo = downloadThreadInfo;
            mTmpFile = tmpFile;
            mPause = false;
            mDownloadListener = downloadListener;
        }

        /**
         * 通知线程需要暂停
         */
        public void pause() {
            mPause = true;
        }

        public boolean isPaused() {
            return mPaused;
        }

        /**
         * 通知线程停止下载
         */
        public void stop() {
            mStop = true;
        }

        public boolean isStoped() {
            return mStoped;
        }

        public DownloadThreadInfo getDownloadThreadInfo() {
            return mDownloadThreadInfo;
        }

        /**
         * 检查是否所有断点任务线程下载完毕
         *
         * @return
         */
        public boolean allTaskFinished() {
            boolean finished = true;
            synchronized (mDownloadFileInfo) {
                List<DownloadTask> downloadTasks = mDownloadTasksMap.get(mDownloadFileInfo.get_id());
                if (downloadTasks != null && downloadTasks.size() > 0) {
                    for (DownloadTask downloadTask : downloadTasks) {
                        if (downloadTask.getDownloadThreadInfo().getStart() < downloadTask.getDownloadThreadInfo().getEnd()) {
                            finished = false;
                            break;
                        }
                    }
                }
            }
            return finished;
        }

        /**
         * 检查是否所有的断点任务线程暂停下载
         *
         * @return
         */
        public boolean allTaskPaused() {
            boolean paused = true;
            synchronized (mDownloadFileInfo) {
                List<DownloadTask> downloadTasks = mDownloadTasksMap.get(mDownloadFileInfo.get_id());
                if (downloadTasks != null && downloadTasks.size() > 0) {
                    for (DownloadTask downloadTask : downloadTasks) {
                        if (downloadTask.getDownloadThreadInfo().getStart() < downloadTask.getDownloadThreadInfo().getEnd()
                                && !downloadTask.isPaused()) {
                            paused = false;
                            break;
                        }
                    }
                }
            }
            return paused;
        }

        /**
         * 检查是否所有的断点任务线程停止下载
         *
         * @return
         */
        public boolean allTaskStoped() {
            boolean stoped = true;
            synchronized (mDownloadFileInfo) {
                List<DownloadTask> downloadTasks = mDownloadTasksMap.get(mDownloadFileInfo.get_id());
                if (downloadTasks != null && downloadTasks.size() > 0) {
                    for (DownloadTask downloadTask : downloadTasks) {
                        if (downloadTask.getDownloadThreadInfo().getStart() < downloadTask.getDownloadThreadInfo().getEnd()
                                && !downloadTask.isStoped()) {
                            stoped = false;
                            break;
                        }
                    }
                }
            }
            return stoped;
        }

        @Override
        public void run() {
            if (mDownloadFileInfo.isSupportRanges() && mDownloadThreadInfo.getStart() >= mDownloadThreadInfo.getEnd()) {
                return;
            }

            RandomAccessFile raf = null;
            InputStream is = null;
            HttpURLConnection conn = null;
            try {
                URL url = new URL(mDownloadFileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setReadTimeout(3000);
                conn.setDoInput(true);
                if(mDownloadFileInfo.isSupportRanges()){ // 支持断点续传
                    conn.setRequestProperty("Range", "bytes=" + mDownloadThreadInfo.getStart() + "-" + mDownloadThreadInfo.getEnd());
                }
//                ScheduleLog.v(TAG, "download task 下载范围：" + mDownloadThreadInfo.getStart() + "-" + mDownloadThreadInfo.getEnd());
//                ScheduleLog.v(TAG, "download task content-range:" + conn.getHeaderField("content-range"));

                if(!mDownloadFileInfo.isSupportRanges()){ // 不支持断点续传
                    // 从0开始下
                    mDownloadThreadInfo.setLength(0);
                    mDownloadThreadInfo.setStart(0);
                }

                conn.connect();

                raf = new RandomAccessFile(mTmpFile, "rwd");
                raf.seek(mDownloadThreadInfo.getStart());

                if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT
                        || conn.getResponseCode() == HttpStatus.SC_OK) { // 断点续传206 非断点续传200
                    int len = 0;
                    byte[] buffer = new byte[BUFFER_SIZE];
                    is = conn.getInputStream();
                    while ((len = is.read(buffer)) != -1) {
                        raf.write(buffer, 0, len);
                        List<DownloadTask> downloadTasks = mDownloadTasksMap.get(mDownloadFileInfo.get_id());
                        if (downloadTasks == null) {
                            return;
                        }
                        mDownloadThreadInfo.setLength(mDownloadThreadInfo.getLength() + len);
                        if(mDownloadFileInfo.isSupportRanges()){ // 断点续传
                            mDownloadThreadInfo.setStart(mDownloadThreadInfo.getStart() + len);
                        }

                        double percent = getDownloadPercent(mDownloadFileInfo, downloadTasks);
                        // 通知回调：下载进度
                        ScheduleLog.v(TAG, "通知回调：下载进度:" + percent);
                        if (mDownloadListener != null) {
                            mDownloadListener.progress(percent);
                        }

                        if (mPause) {

                            mPaused = true;

                            if(mDownloadFileInfo.isSupportRanges()) {
                                // 暂停以后更新状态到数据库
                                DownloadThreadInfoTbl.getInstance().update(mDownloadThreadInfo);
                            }

                            if ((mDownloadFileInfo.isSupportRanges() && allTaskPaused())
                                    || (!mDownloadFileInfo.isSupportRanges())) {
                                mDownloadTasksMap.remove(mDownloadFileInfo.get_id());
                                // 通知回调：所有线程已暂停
                                ScheduleLog.v(TAG, "通知回调：所有线程已暂停");
                                if (mDownloadListener != null) {
                                    mDownloadListener.pause(percent);

                                    if(!mDownloadFileInfo.isSupportRanges()){ // 非断点任务不支持暂停 直接停止
                                        ScheduleLog.v(TAG, "通知回调：所有线程已停止");
                                        mDownloadListener.stop();
                                    }
                                }
                            }

                            return;
                        }

                        if (mStop) {
                            mStoped = true;

                            if ((mDownloadFileInfo.isSupportRanges() && allTaskStoped())
                                    || (!mDownloadFileInfo.isSupportRanges())) {
                                deleteDownloadRecord(mDownloadFileInfo);
                                // 通知回调：所有线程已停止
                                ScheduleLog.v(TAG, "通知回调：所有线程已停止");
                                if (mDownloadListener != null) {
                                    mDownloadListener.stop();
                                }
                            }

                            return;
                        }

                    }


                    // 该线程下载完毕
                    if ((mDownloadFileInfo.isSupportRanges() && allTaskFinished())
                            || (!mDownloadFileInfo.isSupportRanges())) {  // 所有线程下载完毕

                        // 下载完毕将临时文件改名为真正的文件
                        mTmpFile.renameTo(new File(mDownloadFileInfo.getSavePath()));

                        deleteDownloadRecord(mDownloadFileInfo);

                        // 通知回调：所有线程下载完毕
                        ScheduleLog.v(TAG, "通知回调：所有线程下载完毕");
                        if (mDownloadListener != null) {
                            mDownloadListener.finish();
                        }

                    }
                } else {
                    throw new Exception("response code:" + conn.getResponseCode());
                }

            } catch (Exception e) {
                e.printStackTrace();
                synchronized (mDownloadFileInfo) {
                    if (mDownloadListener != null && mDownloadTasksMap.containsKey(mDownloadFileInfo.get_id())) {
                        // 通知回调：下载异常
                        ScheduleLog.v(TAG, "通知回调：下载失败");
                        mDownloadListener.error();
                    }
                    deleteDownloadRecord(mDownloadFileInfo);
                }
            } finally {

                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if (raf != null) {
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if (conn != null) {
                    conn.disconnect();
                }
            }
        }
    }

    /**
     * 下载的回调接口
     */
    public interface DownloadListener {

        /**
         * 下载异常
         */
        public void error();

        /**
         * 正在下载队列 不能重复下载
         */
        public void exist();

        /**
         * 等待下载
         */
        public void waiting();

        /**
         * 开始下载
         *
         * @param percent       当暂停后重新开始下载这里会有百分比进度
         * @param supportRanges 是否断点下载
         */
        public void begin(double percent, boolean supportRanges);

        /**
         * 下载暂停
         *
         * @param percent 当前进度
         */
        public void pause(double percent);

        /**
         * 下载停止(取消)
         */
        public void stop();

        /**
         * 下载完毕
         */
        public void finish();

        /**
         * 下载进度
         *
         * @param percent
         */
        public void progress(double percent);

    }

}
